#include "BufferedServerIoThread.h"

#include <chrono>

static long current_millis();

BufferedServerIoThread::BufferedServerIoThread(
    ProcessSettings settings, int timer_interval, int buffer_size)
    : _thread(settings.process_path, settings.process_args)
    , _buffer_size(buffer_size)
    , _timer_id(0)
    , _timer_interval(timer_interval)
{
    _startTimer();

    connect(this, &BufferedServerIoThread::doStart, this, &BufferedServerIoThread::_start);
    connect(this, &BufferedServerIoThread::doStop, this, &BufferedServerIoThread::_stop);
    connect(&_thread.service(), &ServerIoService::recieved, this, &BufferedServerIoThread::_recieved);
}

BufferedServerIoThread::~BufferedServerIoThread()
{
    _stop();
}

void BufferedServerIoThread::start()
{
    emit doStart();
}

bool BufferedServerIoThread::isRunning()
{
    return _thread.isRunning();
}

void BufferedServerIoThread::stop()
{
    emit doStop();
}

void BufferedServerIoThread::clearBuffer()
{
    QMutexLocker locker (&_buffer_mutex);
    _buffer.clear();
}

void BufferedServerIoThread::_emit_recieved()
{
    QMutexLocker locker (&_buffer_mutex);

    if (_buffer.empty())
    {
        return;
    }

    constexpr int batch = 100;
    if (batch <= _buffer.size())
    {
        QVector<QJsonObject> buffer(_buffer.begin(), _buffer.begin() + batch);
        _buffer.erase(_buffer.begin(), _buffer.begin() + batch);
        emit recieved(buffer);
    }
    else
    {
        QVector<QJsonObject> buffer(_buffer.begin(), _buffer.end());
        _buffer.clear();
        emit recieved(buffer);
    }
}

void BufferedServerIoThread::_start()
{
    _thread.start();
}

void BufferedServerIoThread::_stop()
{
    _thread.stop();
}

void BufferedServerIoThread::_recieved(QVariant message)
{
    if (message.isNull())
    {
        emit recieved({QJsonObject()});
        return;
    }

    auto document = QJsonDocument::fromJson(message.toString().toUtf8());
    auto object = document.isNull() ? QJsonObject() : document.object();

    if (object.isEmpty())
    {
        emit recieved({object});
        return;
    }

    auto message_type_it = object.constFind("type");
    QString message_type = (message_type_it == object.end()) ? "" : message_type_it->toString();
    if (message_type != "message" && message_type != "record")
    {
        emit recieved({object});
        return;
    }

    QMutexLocker locker (&_buffer_mutex);
    _buffer.push_back(object);
    // @attention Приводит к утрате информации
    // @todo Определить допустимость
    // if (size_t(_buffer_size) < _buffer.size())
    // {
    //     _buffer.pop_back();
    // }
}

void BufferedServerIoThread::timerEvent(QTimerEvent * event)
{
    if (event->timerId() != _timer_id)
    {
        QObject::timerEvent(event);
        return;
    }

    _timer_start_time = current_millis();

    _emit_recieved();
}

static long current_millis()
{
    auto time = std::chrono::system_clock::now(); // get the current time
    auto since_epoch = time.time_since_epoch(); // get the duration since epoch
    // I don't know what system_clock returns
    // I think it's uint64_t nanoseconds since epoch
    // Either way this duration_cast will do the right thing
    auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(since_epoch);
    return millis.count(); // just like java (new Date()).getTime();
}

void BufferedServerIoThread::_startTimer()
{
    _stopTimer();

    _timer_id = startTimer(_timer_interval);
    _timer_start_time = current_millis();
}

void BufferedServerIoThread::_stopTimer()
{
    if (_timer_id != 0)
    {
        killTimer(_timer_id);
        _timer_id = 0;
    }
}
